import { Field, Form, Formik } from 'formik' import { trpc, inferMutationInput, QueryError } from '~/utils/trpc' import useDashboard, { useHasPermission } from '~/components/DashboardContext' import { useCallback, useEffect, useMemo, useState } from 'react' import useTable from '~/components/IVTable/useTable' import IVInputField from '~/components/IVInputField' import IVButton from '~/components/IVButton' import PageHeading from '~/components/PageHeading' import { notify } from '~/components/NotificationCenter' import Dialog, { useDialogState } from '~/components/IVDialog' import { OrganizationEnvironment } from '@prisma/client' import IVTooltip from '~/components/IVTooltip' import { useOrgParams } from '~/utils/organization' import { useNavigate } from 'react-router-dom' import { DEVELOPMENT_ORG_ENV_NAME, DEVELOPMENT_ORG_ENV_SLUG, legacy_switchToEnvironment, PRODUCTION_ORG_ENV_NAME, PRODUCTION_ORG_ENV_SLUG, } from '~/utils/environments' import { focusManager } from 'react-query' import SimpleTable from '~/components/SimpleTable' import classNames from 'classnames' import { ENV_COLOR_OPTIONS } from '~/utils/color' import EnvironmentColor from '~/components/EnvironmentColor' type EnvironmentsListItem = Omit & { isLocked: boolean } function EnvFields({ mode, isLoading, error, isLocked, }: { mode: 'create' | 'update' isLoading: boolean error: QueryError | null isLocked: boolean }) { return (
{ if (!val) return 'Please enter a name.' }} required autoFocus={!isLocked} />
{Object.keys(ENV_COLOR_OPTIONS).map(color => ( ))}
{error && (
{error.message ?? ( <> Sorry, there was a problem{' '} {mode === 'create' ? 'creating' : 'updating'} the environment. )}
)}
) } function CreateEnvForm(props: { onSuccess: () => void }) { const createEnv = trpc.useMutation('environments.create') return ( > initialValues={{ name: '', color: 'none', }} onSubmit={async ({ name, color }, { resetForm }) => { if (createEnv.isLoading) return createEnv.mutate( { name, color }, { onSuccess() { resetForm() notify.success(`Environment '${name}' was created.`) props.onSuccess() }, } ) }} > ) } function UpdateEnvForm(props: { env: EnvironmentsListItem onSuccess: () => void }) { const updateEnv = trpc.useMutation('environments.update') const { orgSlug, envSlug } = useOrgParams() const navigate = useNavigate() return ( > initialValues={{ id: props.env.id, name: props.env.name, color: props.env.color || 'none', }} onSubmit={async ({ id, name, color }) => { if (updateEnv.isLoading) return updateEnv.mutate( { id, name, color }, { onSuccess(res) { // replace active envSlug if changed if (envSlug && res.slug !== envSlug) { navigate( `/dashboard/${orgSlug}+${res.slug}/organization/environments`, { replace: true } ) } notify.success(`Environment '${name}' was updated.`) props.onSuccess() }, } ) }} > ) } function EnvrionmentsList({ environments, onUpdate, onEdit, }: { environments: EnvironmentsListItem[] onUpdate: () => void onEdit: (env: EnvironmentsListItem) => void }) { const { envSlug, orgEnvSlug, orgSlug } = useOrgParams() const { mutate: deleteEnv } = trpc.useMutation('environments.delete') const data = useMemo(() => { const onDeleteEnv = (env: EnvironmentsListItem) => { // prevent refetch after confirm dialog is closed focusManager.setFocused(false) if (window.confirm('Are you sure you want to delete this environment?')) { deleteEnv( { id: env.id }, { onSuccess() { notify.success(`Environment '${env.name}' was deleted.`) if (onUpdate) onUpdate() if (env.slug === envSlug) { legacy_switchToEnvironment(orgEnvSlug, orgSlug) } }, onSettled() { focusManager.setFocused(undefined) }, } ) } else { focusManager.setFocused(undefined) } } return environments.map(env => ({ key: env.name, data: { label: ( {env.name} ), slug: ( {env.slug} ), actions: env.deletedAt ? ( Deleted ) : (
), }, })) }, [environments, deleteEnv, onUpdate, envSlug, orgEnvSlug, orgSlug, onEdit]) const table = useTable({ data, columns: ['Name', 'Slug', ''], isSortable: false, shouldCacheRecords: false, }) return } export default function NewEnvironmentPage() { const createEditEnvDialog = useDialogState() const { organization, refetchOrg } = useDashboard() const { refetchQueries } = trpc.useContext() useHasPermission('WRITE_ORG_SETTINGS', { redirectToDashboardHome: true }) const [editingEnv, setEditingEnv] = useState( null ) const onCreateEditSuccess = useCallback(() => { refetchOrg() refetchQueries(['environments.single']) createEditEnvDialog.hide() }, [refetchOrg, createEditEnvDialog, refetchQueries]) const onEdit = useCallback( (env: EnvironmentsListItem) => { setEditingEnv(env) createEditEnvDialog.show() }, [createEditEnvDialog] ) useEffect(() => { if (!createEditEnvDialog.visible && !createEditEnvDialog.animating) { setEditingEnv(null) } }, [createEditEnvDialog.visible, createEditEnvDialog.animating]) const { environments } = organization const allEnvironments = useMemo(() => { const allEnvironments: EnvironmentsListItem[] = [ ...environments.map(env => ({ ...env, isLocked: env.slug === 'production' || env.slug === null || env.slug === DEVELOPMENT_ORG_ENV_SLUG, })), ] if (!allEnvironments.find(env => env.slug === PRODUCTION_ORG_ENV_SLUG)) { // Show a dummy production environment if the user hasn't created an environments yet. // This will be replaced by a real prod environment when they create their first env. allEnvironments.push({ id: PRODUCTION_ORG_ENV_NAME.toLowerCase(), name: PRODUCTION_ORG_ENV_NAME, slug: PRODUCTION_ORG_ENV_SLUG, createdAt: new Date(), updatedAt: new Date(), deletedAt: null, isLocked: true, color: null, }) } if (!allEnvironments.find(env => env.slug === DEVELOPMENT_ORG_ENV_SLUG)) { // Show a dummy development environment if the user hasn't created an environments yet. // This will be replaced by a real prod environment when they create their first env. allEnvironments.push({ id: DEVELOPMENT_ORG_ENV_SLUG, name: DEVELOPMENT_ORG_ENV_NAME, slug: DEVELOPMENT_ORG_ENV_SLUG, createdAt: new Date(), updatedAt: new Date(), deletedAt: null, isLocked: true, color: null, }) } // Make sure production and development are first and second allEnvironments.sort((a, b) => { if (a.name === PRODUCTION_ORG_ENV_NAME) return -1 if (b.name === PRODUCTION_ORG_ENV_NAME) return 1 if (a.name === DEVELOPMENT_ORG_ENV_NAME) return -1 if (b.name === DEVELOPMENT_ORG_ENV_NAME) return 1 return 0 }) return allEnvironments }, [environments]) return (
{editingEnv ? ( ) : ( )}
) }